/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File Subscription.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.flow.NoSuchSubscription;
import il.ac.biu.cs.grossmm.api.flow.NodeEventInspector;
import il.ac.biu.cs.grossmm.api.flow.NotificationProvider;
import il.ac.biu.cs.grossmm.api.flow.Subscriber;
import il.ac.biu.cs.grossmm.api.flow.Status;
import il.ac.biu.cs.grossmm.api.flow.Subscription;
import il.ac.biu.cs.grossmm.api.keys.Key;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Represents a shared subscription of serveral event consumers to the
 * notification about changes in the same resource provided by a notification
 * provider.
 */
public class SubscriptionImpl<N> implements Subscription<N> {
	private final Key key;

	private Root<N> root;

	private Status state;

	private final Set<Subscriber<N>> subscribers;

	NotificationProvider<N> provider;

	Lock statusLock = new ReentrantLock();

	SubscriptionImpl(Key key, Root<N> root, NotificationProvider<N> provider) {
		this(key, root, provider, true);
	}

	public SubscriptionImpl(Key key, il.ac.biu.cs.grossmm.api.data.Root<N> root,
			NotificationProvider<N> provider, boolean b) {
		this.key = key;
		this.provider = provider;
		this.root = (Root<N>) root;

		this.subscribers = new HashSet<Subscriber<N>>();

		if (root == null)
			deactivate(Status.INIT);
		else {
			((Root<N>) root).setSubscription(this);
			if (b)
				this.state = Status.ACTIVE;
			else
				this.state = Status.ACTIVATING;
		}
	}

	NodeType getNodeType() {
		return provider.getNodeType();
	}

	/**
	 * Called when the data node has been updated
	 * 
	 */
	@SuppressWarnings("unchecked")
	void nodeUpdated(NodeEventInspector inspector) {
		statusLock.lock();

		if (state == Status.ACTIVATING) {
			state = Status.ACTIVE;
			statusLock.unlock();
			for (Subscriber subscriber : subscribers)
				subscriber.activated(key, root);
			// NB: subscribers are not notified about the initial data change
		} else {
			statusLock.unlock();
			assert state == Status.ACTIVE;

			for (Subscriber subscriber : subscribers)
				subscriber.notify(root, inspector);
		}
	}

	/**
	 * Closes the subscription: causes the notification provider to stop sending
	 * notifications for this subscription.
	 */
	public void close() {
		provider.unsubscribe(key);

		if (root != null)
			root.setSubscription(null);

		state = null; // should never be used by anyone
		root = null;
	}

	/**
	 * Register susbcriber for receiving notifications
	 * 
	 * @param subscriber
	 *            the subscriber to register for receiving notifications
	 */
	void add(Subscriber<N> subscriber) {
		assert subscriber != null;

		subscribers.add(subscriber);
	}

	/**
	 * Deregister subscriber from receiving notifications
	 * 
	 * @param subscriber
	 *            the subscriber to deregister from receiving notifications
	 */
	boolean remove(Subscriber<N> sp) {
		boolean b = subscribers.remove(sp);

		if (subscribers.size() == 0)
			close();

		return b;
	}

	/**
	 * Gets the status of the subscription.
	 * 
	 * @return the status of the subscription
	 * 
	 * @see Status
	 */
	public Status getStatus() {
		return state;
	}

	/**
	 * Deactivates subscription and sets its state.
	 * 
	 * @param state
	 *            the status to set
	 */
	public synchronized void deactivate(Status state) {
		statusLock.lock();
		if (root != null)
			root.setSubscription(null);
		this.root = null;
		this.state = state;
		statusLock.unlock();

		for (Subscriber subscriber : subscribers)
			try {
				subscriber.deactivated(key, state);
			} catch (NoSuchSubscription ignored) {
				ignored.printStackTrace();
			}
	}

	/**
	 * Activates subscription.
	 * 
	 * @param root
	 *            root of data tree associated with this subscription
	 */
	public synchronized void activate(il.ac.biu.cs.grossmm.api.data.Root<N> root) {
		statusLock.lock();
		this.root = (Root<N>) root;

		// set stat
		this.state = Status.ACTIVATING;
		((Root<N>) root).setSubscription(this);
		statusLock.unlock();
	}

	public Key getKey() {
		return key;
	}

	/**
	 * Gets root node associated with this subscription
	 * 
	 * @return root node associated with this subscription
	 */
	public Root<N> getRoot() {
		return root;
	}
}
